package io.github.wanggit.antrpc.client.config.listeners;

import io.github.wanggit.antrpc.AntrpcContext;
import io.github.wanggit.antrpc.client.config.IConfigChangeListener;
import io.github.wanggit.antrpc.client.config.workers.ChangeWorkerHelper;
import io.github.wanggit.antrpc.commons.annotations.Configurable;
import io.github.wanggit.antrpc.commons.bean.ConfigItem;
import io.github.wanggit.antrpc.commons.config.ConfigChangeTypeEnum;
import io.github.wanggit.antrpc.commons.config.Configuration;
import io.github.wanggit.antrpc.commons.config.IConfiguration;
import io.github.wanggit.antrpc.commons.config.workers.IChangeWorker;
import io.github.wanggit.antrpc.commons.constants.ConstantValues;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class DefaultConfigChangeListener implements IConfigChangeListener {

    private final IConfiguration configuration;

    private final AntrpcContext antrpcContext;

    /**
     *
     *
     * <pre>
     * Examples:
     * io.github.wanggit.antrpc.client.config.workers.CallLogReporterConfigChangeWorker -> [antrpc.callLogReporterConfig., antrpc.call-log-reporter-config.]
     * </pre>
     */
    private final Map<Class<? extends IChangeWorker>, List<String>> configurables = new HashMap<>();

    public DefaultConfigChangeListener(IConfiguration configuration, AntrpcContext antrpcContext) {
        if (log.isInfoEnabled()) {
            log.info("Init Default Config Changed Listener...");
        }
        this.configuration = configuration;
        this.antrpcContext = antrpcContext;
        initConfigurables();
    }

    @Override
    public void changed(
            ConfigChangeTypeEnum changeType,
            ConfigItem configItem,
            String oldValue,
            String newValue) {
        // 与AntRPC本身无关的配置忽略
        String key = configItem.getKey();
        if (!key.startsWith(ConstantValues.ANTRPC_CONFIG_PREFIX + ".")) {
            return;
        }
        // 只有配置项标记了 @Configurable 注解的才能做动态修改
        Tuple2<List<String>, Class<? extends IChangeWorker>> tuple2 = isConfigurable(key);
        if (null != tuple2) {
            if (log.isInfoEnabled()) {
                log.info(
                        "The " + key + " changed. oldValue=" + oldValue + ", newValue=" + newValue);
            }
            ChangeWorkerHelper.createWorker(tuple2, antrpcContext)
                    .fired(changeType, key, oldValue, newValue);
        } else {
            if (log.isWarnEnabled()) {
                log.warn(
                        "The "
                                + key
                                + " configuration has changed, but it is locally immutable. Ignored.");
            }
        }
    }

    private Tuple2<List<String>, Class<? extends IChangeWorker>> isConfigurable(String configName) {
        for (Map.Entry<Class<? extends IChangeWorker>, List<String>> entry :
                configurables.entrySet()) {
            for (String prefix : entry.getValue()) {
                if (configName.startsWith(prefix)) {
                    return Tuple.of(entry.getValue(), entry.getKey());
                }
            }
        }
        return null;
    }

    /** 查看 io.github.wanggit.antrpc.commons.config.Configuration 中的那些属性标记了 @Configurable 注解 */
    private void initConfigurables() {
        Field[] fields = Configuration.class.getDeclaredFields();
        for (Field field : fields) {
            ReflectionUtils.makeAccessible(field);
            Configurable configurable = AnnotationUtils.findAnnotation(field, Configurable.class);
            if (null != configurable) {
                configurables.put(
                        configurable.changeWorker(),
                        List.of(
                                ConstantValues.ANTRPC_CONFIG_PREFIX + "." + field.getName() + ".",
                                ConstantValues.ANTRPC_CONFIG_PREFIX
                                        + "."
                                        + field.getName().replaceAll("([A-Z])", "-$1").toLowerCase()
                                        + "."));
            }
        }
    }
}
